﻿/**
 * 
 * Copyright 2009 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace mooseirc.irc
{

    /// <summary>
    /// Our IRC Client
    /// </summary>
    /// <remarks>
    /// If we get some data that comes in, and we are in a connected (but not registered) state,
    /// then we need to register.
    /// 
    /// I've short circuted this alot, just to keep things moving along.
    /// 
    /// Specifically, the IrcState.  Due to the fact that im using the enumeration directly, 
    /// setting the state in commands has no meaningful effect.
    /// 
    /// Will likley create an IrcStatus object, that has an enumeration, and other stuff inside.
    /// 
    /// </remarks>
    [CLSCompliant(true)]
    public sealed class IrcClient
    {

        /// <summary>
        /// Connection Object
        /// </summary>
        private IrcConnection _connection;

        /// <summary>
        /// Current Irc State
        /// </summary>
        private IrcState _state;

        /// <summary>
        /// Standard Output
        /// </summary>
        private OutputStream _output;

        /// <summary>
        /// Gets Current Irc State
        /// </summary>
        public IrcState State
        {
            get
            {
                return _state;
            }
        }

        /// <summary>
        /// Event Signalling a standard message, that probably should be printed
        /// </summary>
        public event ResponseHandler StandardMessage
        {
            add
            {
                _output.WrittenTo += value;
            }
            remove
            {
                _output.WrittenTo -= value;
            }
        }

        /// <summary>
        /// Initialize Irc Connection
        /// </summary>
        public IrcClient()
            : base()
        {
            _connection = new IrcConnection();
            _connection.Response += onResponse;


            _output = new OutputStream();


            _state = IrcState.Disconnected;
            return;
        }

        /// <summary>
        /// Connect to Irc Server
        /// </summary>
        /// <param name="host">host to connect to</param>
        /// <param name="port">port to connect to</param>
        public void connect(string host, int port)
        {
            IrcCommand connect = utility.DriverUtility.getCommand("server");
            connect.execute(_connection, _state, host + ":" + port, _output);
            _state = IrcState.Connected;
            return;
        }

        /// <summary>
        /// Disconnect from IRC Server
        /// </summary>
        public void disconnect()
        {
            IrcCommand disconnect = utility.DriverUtility.getCommand("disconnect");
            disconnect.execute(_connection, _state, string.Empty, _output);
            _state = IrcState.Disconnected;
            return;
        }

        /// <summary>
        /// Send Password to IRC Server, for Connection Registration
        /// </summary>
        /// <param name="password">password to send</param>
        public void password(string password)
        {
            IrcCommand password_command = utility.DriverUtility.getCommand("pass");
            password_command.execute(_connection, _state, password, _output);
            return;
        }

        /// <summary>
        /// Send Nick Command to server, this has to be done for server registration,
        /// but user may do this whenever
        /// </summary>
        /// <param name="nickname">new desired nickname</param>
        public void nick(string nickname)
        {
            IrcCommand nick_command = utility.DriverUtility.getCommand("nick");
            nick_command.execute(_connection, _state, nickname, _output);
            return;
        }

        public void send_raw(string raw_command)
        {
            _connection.send(raw_command);
            return;
        }

        /// <summary>
        /// Send User Command to server, this completes user registration
        /// </summary>
        public void user(string username, string hostname, string servername, string realname)
        {

            //TODO: Review user registration

            IrcCommand user_command = utility.DriverUtility.getCommand("user");
            user_command.execute(_connection, _state, username + " 8 * :" + realname, _output);
            _state = IrcState.Registered;
            return;
        }

        /// <summary>
        /// Fires when server sends a response
        /// </summary>
        /// <param name="response">response from server</param>
        private void onResponse(string response)
        {

            string[] responses = response.Split(
                new string[] { "\r\n" },
                StringSplitOptions.RemoveEmptyEntries);
            string[] parsed_response;
            IrcResponse response_object = null;
            foreach (string line in responses)
            {

                if (_state == IrcState.Connected)
                {
                    parsed_response = parseBNFPreAuth(line);
                    byte[] buffer = Encoding.UTF8.GetBytes(parsed_response[0]);
                    _output.Write(buffer, 0, buffer.Length);
                }
                else
                {
                    parsed_response = parseBNF(line);
                    //I dont think I need to worry abuot this...
                    //if (parsed_response[0] == string.Empty)
                    //    continue;

                    response_object = utility.DriverUtility.getResponse(parsed_response[0]);
                    if (response_object != null)
                    {
                        response_object.execute(_connection, _state, parsed_response, _output); //For now, send everything in, except the command itself
                    }
                    else
                    { //Write the response
                        byte[] buffer = Encoding.UTF8.GetBytes(response);
                        _output.Write(buffer, 0, buffer.Length);
                    }
                }
            }

            return;
        }

        static string[] parseBNFPreAuth(string input)
        {
            return new string[] { input };
        }

        static string[] parseBNF(string input)
        {
            string[] strTok = input.Split(
                new char[] { ' ' },
                StringSplitOptions.RemoveEmptyEntries);
            List<string> parsed = new List<string>();
            int parms_start = 0;
            if (strTok[0][0] == ':' && strTok[0].Length > 1)
            {
                Console.WriteLine("FROM: " + strTok[0]);
                parsed.Add(strTok[1]); //This is the command
                parms_start = 2;
            }
            else
            {
                parsed.Add(strTok[0]); //This is the command, likely PING
                parms_start = 1;
            }

            if (parms_start < strTok.Length)
            {
                bool bLast = false;
                for (int i = parms_start; i < strTok.Length; i++)
                {
                    if (strTok[i][0] == ':')
                    {
                        parsed.Add(strTok[i]);
                        bLast = true;
                    }
                    else if (bLast)
                    {
                        parsed[parsed.Count - 1] += " " + strTok[i];
                    }
                    else
                    {
                        parsed.Add(strTok[i]);
                    }
                }
            }

            return parsed.ToArray();
        }
    }
}
